home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / chrome / calendar.jar / content / calendar / sun-calendar-event-dialog-freebusy.xml < prev    next >
Extensible Markup Language  |  2007-11-02  |  60KB  |  1,650 lines

  1. <?xml version="1.0"?>
  2. <!-- ***** BEGIN LICENSE BLOCK *****
  3.    - Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.    -
  5.    - The contents of this file are subject to the Mozilla Public License Version
  6.    - 1.1 (the "License"); you may not use this file except in compliance with
  7.    - the License. You may obtain a copy of the License at
  8.    - http://www.mozilla.org/MPL/
  9.    -
  10.    - Software distributed under the License is distributed on an "AS IS" basis,
  11.    - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.    - for the specific language governing rights and limitations under the
  13.    - License.
  14.    -
  15.    - The Original Code is Sun Microsystems code.
  16.    -
  17.    - The Initial Developer of the Original Code is Sun Microsystems.
  18.    - Portions created by the Initial Developer are Copyright (C) 2006
  19.    - the Initial Developer. All Rights Reserved.
  20.    -
  21.    - Contributor(s):
  22.    -   Michael Buettner <michael.buettner@sun.com>
  23.    -
  24.    - Alternatively, the contents of this file may be used under the terms of
  25.    - either the GNU General Public License Version 2 or later (the "GPL"), or
  26.    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.    - in which case the provisions of the GPL or the LGPL are applicable instead
  28.    - of those above. If you wish to allow use of your version of this file only
  29.    - under the terms of either the GPL or the LGPL, and not to allow others to
  30.    - use your version of this file under the terms of the MPL, indicate your
  31.    - decision by deleting the provisions above and replace them with the notice
  32.    - and other provisions required by the GPL or the LGPL. If you do not delete
  33.    - the provisions above, a recipient may use your version of this file under
  34.    - the terms of any one of the MPL, the GPL or the LGPL.
  35.    -
  36.    - ***** END LICENSE BLOCK ***** -->
  37.  
  38. <!DOCTYPE dialog [
  39.   <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
  40.   <!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
  41.   <!ENTITY % dtd3 SYSTEM "chrome://calendar/locale/sun-calendar-event-dialog.dtd" > %dtd3;
  42. ]>
  43.  
  44. <bindings xmlns="http://www.mozilla.org/xbl"
  45.           xmlns:xbl="http://www.mozilla.org/xbl"
  46.           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  47.  
  48.   <!--
  49.   ########################################################################
  50.   ## scroll-container
  51.   ########################################################################
  52.   -->
  53.   <binding id="scroll-container" extends="xul:box">
  54.     <content>
  55.       <xul:box class="container"
  56.                xbl:inherits="flex"
  57.                anonid="container"
  58.                style="overflow: hidden; clip: rect(0px 0px 0px 0px);">
  59.         <xul:box class="content"
  60.                  xbl:inherits="flex,orient"
  61.                  anonid="content">
  62.           <children/>
  63.         </xul:box>
  64.       </xul:box>
  65.     </content>
  66.  
  67.     <implementation>
  68.       <property name="x">
  69.         <setter><![CDATA[
  70.           var content =
  71.               document.getAnonymousElementByAttribute(
  72.                   this, "anonid", "content");
  73.           content.setAttribute("style",
  74.                                "margin-left: " + (-val) + "px;");
  75.           return val;
  76.         ]]></setter>
  77.       </property>
  78.  
  79.       <property name="y">
  80.         <setter><![CDATA[
  81.           var content =
  82.               document.getAnonymousElementByAttribute(
  83.                   this, "anonid", "content");
  84.           content.setAttribute("style",
  85.                                "margin-top: " + (-val) + "px;");
  86.           return val;
  87.         ]]></setter>
  88.       </property>
  89.     </implementation>
  90.   </binding>
  91.  
  92.   <!--
  93.   ########################################################################
  94.   ## freebusy-day
  95.   ########################################################################
  96.   -->
  97.   <binding id="freebusy-day" extends="xul:box">
  98.     <content>
  99.       <xul:box orient="vertical">
  100.         <xul:text class="freebusy-timebar-title"
  101.                   style="font-weight:bold;"
  102.                   anonid="day"/>
  103.         <xul:box equalsize="always" anonid="hours">
  104.           <xul:text class="freebusy-timebar-hour"/>
  105.         </xul:box>
  106.       </xul:box>
  107.     </content>
  108.  
  109.     <implementation>
  110.       <field name="mDateFormatter">null</field>
  111.       <field name="mStartDate">null</field>
  112.       <field name="mEndDate">null</field>
  113.       <field name="mStartHour">0</field>
  114.       <field name="mEndHour">24</field>
  115.       <field name="mForce24Hours">false</field>
  116.       <field name="mZoomFactor">100</field>
  117.  
  118.       <property name="zoomFactor">
  119.         <getter><![CDATA[
  120.           return this.mZoomFactor;
  121.         ]]></getter>
  122.         <setter><![CDATA[
  123.           this.mZoomFactor = val;
  124.           var hours =
  125.               document.getAnonymousElementByAttribute(
  126.                   this, "anonid", "hours");
  127.           while (hours.childNodes.length > 1) {
  128.               hours.removeChild(hours.lastChild);
  129.           }
  130.           return val;
  131.         ]]></setter>
  132.       </property>
  133.  
  134.       <property name="force24Hours">
  135.         <getter><![CDATA[
  136.             return this.mForce24Hours;
  137.         ]]></getter>
  138.         <setter><![CDATA[
  139.           this.mForce24Hours = val;
  140.           this.initTimeRange();
  141.  
  142.           var hours =
  143.               document.getAnonymousElementByAttribute(
  144.                   this, "anonid", "hours");
  145.           while (hours.childNodes.length > 1) {
  146.               hours.removeChild(hours.lastChild);
  147.           }
  148.  
  149.           return val;
  150.         ]]></setter>
  151.       </property>
  152.  
  153.       <constructor><![CDATA[
  154.         this.initTimeRange();
  155.       ]]></constructor>
  156.  
  157.       <method name="initTimeRange">
  158.         <body><![CDATA[
  159.           if (this.force24Hours) {
  160.               this.mStartHour = 0;
  161.               this.mEndHour = 24;
  162.           } else {
  163.               this.mStartHour = getPrefSafe("calendar.view.daystarthour", 8);
  164.               this.mEndHour = getPrefSafe("calendar.view.dayendhour", 19);
  165.           }
  166.         ]]></body>
  167.       </method>
  168.  
  169.       <property name="startDate">
  170.         <setter><![CDATA[
  171.           this.mStartDate = val.clone();
  172.           this.mStartDate.minute = 0;
  173.           this.mStartDate.second = 0;
  174.           this.mStartDate.makeImmutable();
  175.           return val;
  176.         ]]></setter>
  177.       </property>
  178.  
  179.       <property name="endDate">
  180.         <setter><![CDATA[
  181.           this.mEndDate = val.clone();
  182.           this.mEndDate.makeImmutable();
  183.           return val;
  184.         ]]></setter>
  185.       </property>
  186.  
  187.       <property name="dayHeight">
  188.         <getter><![CDATA[
  189.           var day =
  190.               document.getAnonymousElementByAttribute(
  191.                   this, "anonid", "day");
  192.           return day.boxObject.height;
  193.         ]]></getter>
  194.       </property>
  195.  
  196.       <property name="date">
  197.         <setter><![CDATA[
  198.           var date = val.clone();
  199.           date.hour = 0;
  200.           date.minute = 0;
  201.           date.isDate = false;
  202.  
  203.           if (!this.mDateFormatter) {
  204.               this.mDateFormatter =
  205.                   Components.classes[
  206.                       "@mozilla.org/calendar/datetime-formatter;1"]
  207.                           .getService(
  208.                               Components.interfaces.calIDateTimeFormatter);
  209.           }
  210.  
  211.           // First set the formatted date string as title
  212.           var day =
  213.               document.getAnonymousElementByAttribute(
  214.                   this, "anonid", "day");
  215.           var value = (this.mZoomFactor > 100) ?
  216.               this.mDateFormatter.formatDateShort(date) :
  217.               this.mDateFormatter.formatDateLong(date);
  218.           day.setAttribute("value", value);
  219.  
  220.           // Now create as many 'hour' elements as needed
  221.           var step_in_minutes = Math.floor(60 * this.mZoomFactor / 100);
  222.           var hours =
  223.               document.getAnonymousElementByAttribute(
  224.                   this, "anonid", "hours");
  225.           date.hour = this.mStartHour;
  226.           if (hours.childNodes.length <= 1) {
  227.               var template = hours.childNodes[0];
  228.               var time = this.mDateFormatter.formatTime(date);
  229.               template.setAttribute("value", time);
  230.               date.minute += step_in_minutes;
  231.               var count = Math.ceil(
  232.                   (this.mEndHour - this.mStartHour) * 60 / step_in_minutes);
  233.               var remain = count - 1;
  234.               while (remain--) {
  235.                   var newNode = template.cloneNode(false);
  236.                   var value = this.mDateFormatter.formatTime(date);
  237.                   newNode.setAttribute("value", value);
  238.                   hours.appendChild(newNode);
  239.                   date.minute += step_in_minutes;
  240.               }
  241.           }
  242.  
  243.           return val;
  244.         ]]></setter>
  245.       </property>
  246.     </implementation>
  247.   </binding>
  248.  
  249.   <!--
  250.   ########################################################################
  251.   ## freebusy-timebar
  252.   ########################################################################
  253.   -->
  254.   <binding id="freebusy-timebar" extends="xul:box">
  255.     <content>
  256.       <xul:listbox anonid="listbox"
  257.                    seltype="multiple"
  258.                    rows="1"
  259.                    flex="1"
  260.                    disabled="true">
  261.         <xul:listcols>
  262.           <xul:listcol id="day-column" flex="1"/>
  263.         </xul:listcols>
  264.         <xul:listitem anonid="item" allowevents="true">
  265.           <xul:listcell >
  266.             <xul:scroll-container anonid="container">
  267.               <xul:freebusy-day anonid="template"/>
  268.             </xul:scroll-container>
  269.           </xul:listcell>
  270.         </xul:listitem>
  271.       </xul:listbox>
  272.     </content>
  273.  
  274.     <implementation>
  275.       <field name="mNumDays">0</field>
  276.       <field name="mRange">0</field>
  277.       <field name="mStartDate">null</field>
  278.       <field name="mEndDate">null</field>
  279.       <field name="mDayOffset">0</field>
  280.       <field name="mScrollOffset">0</field>
  281.       <field name="mStartHour">0</field>
  282.       <field name="mEndHour">24</field>
  283.       <field name="mForce24Hours">false</field>
  284.       <field name="mZoomFactor">100</field>
  285.  
  286.       <property name="zoomFactor">
  287.         <getter><![CDATA[
  288.           return this.mZoomFactor;
  289.         ]]></getter>
  290.         <setter><![CDATA[
  291.           this.mZoomFactor = val;
  292.  
  293.           var template =
  294.               document.getAnonymousElementByAttribute(
  295.                   this, "anonid", "template");
  296.           var parent = template.parentNode;
  297.           while (parent.childNodes.length > 1) {
  298.               parent.removeChild(parent.lastChild);
  299.           }
  300.  
  301.           template.force24Hours = this.mForce24Hours;
  302.           template.zoomFactor = this.mZoomFactor;
  303.  
  304.           this.onLoad();
  305.  
  306.           return val;
  307.         ]]></setter>
  308.       </property>
  309.  
  310.       <property name="force24Hours">
  311.         <getter><![CDATA[
  312.           return this.mForce24Hours;
  313.         ]]></getter>
  314.         <setter><![CDATA[
  315.           this.mForce24Hours = val;
  316.           this.initTimeRange();
  317.  
  318.           var template =
  319.               document.getAnonymousElementByAttribute(
  320.                   this, "anonid", "template");
  321.  
  322.           var parent = template.parentNode;
  323.           while (parent.childNodes.length > 1) {
  324.             parent.removeChild(parent.lastChild);
  325.           }
  326.  
  327.           template.force24Hours = this.mForce24Hours;
  328.           template.zoomFactor = this.mZoomFactor;
  329.  
  330.           this.onLoad();
  331.  
  332.           return val;
  333.         ]]></setter>
  334.       </property>
  335.  
  336.       <property name="contentWidth">
  337.         <getter><![CDATA[
  338.           // Calculate the difference between the first to day-elements, since
  339.           // the width of the head element does not specify the width we need
  340.           // due to an arbitrary margin value.
  341.           var template =
  342.               document.getAnonymousElementByAttribute(
  343.                   this, "anonid", "template");
  344.           return template.nextSibling.boxObject.x - template.boxObject.x;
  345.         ]]></getter>
  346.       </property>
  347.  
  348.       <property name="containerWidth">
  349.         <getter><![CDATA[
  350.           return this.parentNode.boxObject.width;
  351.         ]]></getter>
  352.       </property>
  353.  
  354.       <property name="startDate">
  355.         <setter><![CDATA[
  356.           this.mStartDate = val.clone();
  357.           this.mStartDate.makeImmutable();
  358.           return val;
  359.         ]]></setter>
  360.         <getter><![CDATA[
  361.           return this.mStartDate;
  362.         ]]></getter>
  363.       </property>
  364.  
  365.       <property name="endDate">
  366.         <setter><![CDATA[
  367.           this.mEndDate = val.clone();
  368.           this.mEndDate.makeImmutable();
  369.           return val;
  370.         ]]></setter>
  371.         <getter><![CDATA[
  372.           return this.mEndDate;
  373.         ]]></getter>
  374.       </property>
  375.  
  376.       <property name="dayOffset">
  377.         <setter><![CDATA[
  378.           this.mDayOffset = val;
  379.           var container =
  380.               document.getAnonymousElementByAttribute(
  381.                   this, "anonid", "container");
  382.           var date = this.mStartDate.clone();
  383.           date.day += val;
  384.           var numChilds = container.childNodes.length;
  385.           for (var i = 0; i < numChilds; i++) {
  386.               var child = container.childNodes[i];
  387.               child.date = date;
  388.               date.day++;
  389.           }
  390.           return val;
  391.         ]]></setter>
  392.       </property>
  393.  
  394.       <property name="step">
  395.         <getter><![CDATA[
  396.           // How much pixels spans a single day
  397.           var oneday = this.contentWidth;
  398.  
  399.           // The difference in pixels between the content and the container.
  400.           var shift = (oneday * this.mRange) - (this.containerWidth);
  401.  
  402.           // What we want to know is the scale of the total shift
  403.           // needed to step one block further. Since the content
  404.           // is divided into 'numHours' equal parts, we can simply state:
  405.           var numHours = this.mEndHour - this.mStartHour;
  406.           return (this.contentWidth) / (numHours * shift);
  407.         ]]></getter>
  408.       </property>
  409.  
  410.       <property name="scroll">
  411.         <setter><![CDATA[
  412.           this.mScrollOffset = val;
  413.  
  414.           // How much pixels spans a single day
  415.           var oneday = this.contentWidth;
  416.  
  417.           // The difference in pixels between the content and the container.
  418.           var shift = (oneday * this.mRange) - (this.containerWidth);
  419.  
  420.           // Now calculate the (positive) offset in pixels which the content
  421.           // needs to be shifted. This is a simple scaling in one dimension.
  422.           var offset = Math.floor(val * shift);
  423.  
  424.           // Now find out how much days this offset effectively skips.
  425.           // this is a simple division which always yields a positive integer value.
  426.           this.dayOffset = (offset - (offset % oneday)) / oneday;
  427.  
  428.           // Set the pixel offset for the content which will always need
  429.           // to be in the range [0 <= offset <= oneday].
  430.           offset %= oneday;
  431.  
  432.           // Set the offset at the content node.
  433.           var container =
  434.               document.getAnonymousElementByAttribute(
  435.                   this, "anonid", "container");
  436.           container.x = offset;
  437.           return val;
  438.         ]]></setter>
  439.         <getter><![CDATA[
  440.           return this.mScrollOffset;
  441.         ]]></getter>
  442.       </property>
  443.  
  444.       <constructor><![CDATA[
  445.         var args = window.arguments[0];
  446.         var startTime = args.startTime;
  447.         var endTime = args.endTime;
  448.  
  449.         this.initTimeRange();
  450.  
  451.         // The basedate is the date/time from which the display
  452.         // of the timebar starts. The range is the number of days
  453.         // we should be able to show. The start- and enddate
  454.         // is the time the event is scheduled for.
  455.         var kDefaultTimezone = calendarDefaultTimezone();
  456.         this.startDate = startTime.getInTimezone(kDefaultTimezone);
  457.         this.endDate = endTime.getInTimezone(kDefaultTimezone);
  458.         this.mRange = Number(this.getAttribute("range"));
  459.  
  460.         var self = this;
  461.         var load = function loadHandler() {
  462.             self.onLoad();
  463.         };
  464.         window.addEventListener("load", load, true);
  465.       ]]></constructor>
  466.  
  467.       <method name="refresh">
  468.         <body><![CDATA[
  469.           var date = this.mStartDate.clone();
  470.           var template =
  471.               document.getAnonymousElementByAttribute(
  472.                   this, "anonid", "template");
  473.           var parent = template.parentNode;
  474.           var numChilds = parent.childNodes.length;
  475.           for (var i = 0; i < numChilds; i++) {
  476.               var child = parent.childNodes[i];
  477.               child.startDate = this.mStartDate;
  478.               child.endDate = this.mEndDate;
  479.               child.date = date;
  480.               date.day++;
  481.           }
  482.           var offset = this.mDayOffset;
  483.           this.dayOffset = offset;
  484.         ]]></body>
  485.       </method>
  486.  
  487.       <method name="onLoad">
  488.         <body><![CDATA[
  489.             this.initialize();
  490.             var template =
  491.                 document.getAnonymousElementByAttribute(
  492.                     this, "anonid", "template");
  493.             var event = document.createEvent('Events');
  494.             event.initEvent('timebar', true, false);
  495.             event.details = this.contentWidth;
  496.             event.height = template.dayHeight;
  497.             this.dispatchEvent(event);
  498.         ]]></body>
  499.       </method>
  500.  
  501.       <method name="initialize">
  502.         <body><![CDATA[
  503.           var args = window.arguments[0];
  504.           var startTime = args.startTime;
  505.           var endTime = args.endTime;
  506.           var calendar = args.calendar;
  507.  
  508.           var kDefaultTimezone = calendarDefaultTimezone();
  509.           this.startDate = startTime.getInTimezone(kDefaultTimezone);
  510.           this.endDate = endTime.getInTimezone(kDefaultTimezone);
  511.  
  512.           // Set the number of 'freebusy-day'-elements
  513.           // we need to fill up the content box.
  514.           // TODO: hardcoded value
  515.           this.mNumDays = 4 * this.mZoomFactor / 100;
  516.           if (this.mNumDays < 2) {
  517.               this.mNumDays = 2;
  518.           }
  519.  
  520.           // Now create those elements and set their date property.
  521.           var date = this.mStartDate.clone();
  522.           var template =
  523.               document.getAnonymousElementByAttribute(
  524.                   this, "anonid", "template");
  525.           template.force24Hours = this.mForce24Hours;
  526.           template.zoomFactor = this.mZoomFactor;
  527.           template.startDate = this.mStartDate;
  528.           template.endDate = this.mEndDate;
  529.           template.date = date;
  530.           var parent = template.parentNode;
  531.           if (parent.childNodes.length <= 1) {
  532.               var count = this.mNumDays - 1;
  533.               if (count > 0) {
  534.                   for (var i = 0; i < count; i++) {
  535.                       date.day++;
  536.                       var newNode = template.cloneNode(false);
  537.                       newNode.force24Hours = this.mForce24Hours;
  538.                       newNode.zoomFactor = this.mZoomFactor;
  539.                       newNode.startDate = this.mStartDate;
  540.                       newNode.endDate = this.mEndDate;
  541.                       newNode.date = date;
  542.                       parent.appendChild(newNode);
  543.                   }
  544.               }
  545.           }
  546.         ]]></body>
  547.       </method>
  548.  
  549.       <method name="initTimeRange">
  550.         <body><![CDATA[
  551.           if (this.force24Hours) {
  552.               this.mStartHour = 0;
  553.               this.mEndHour = 24;
  554.           } else {
  555.               this.mStartHour = getPrefSafe("calendar.view.daystarthour", 8);
  556.               this.mEndHour = getPrefSafe("calendar.view.dayendhour", 19);
  557.           }
  558.         ]]></body>
  559.       </method>
  560.     </implementation>
  561.   </binding>
  562.  
  563.   <!--
  564.   ########################################################################
  565.   ## freebusy-row
  566.   ########################################################################
  567.   -->
  568.   <binding id="freebusy-row" extends="xul:box">
  569.     <content>
  570.       <xul:scroll-container flex="1" anonid="container">
  571.         <xul:box equalsize="always" anonid="hours">
  572.           <xul:text class="freebusy-grid"/>
  573.         </xul:box>
  574.       </xul:scroll-container>
  575.     </content>
  576.  
  577.     <implementation>
  578.       <field name="mState">null</field>
  579.       <field name="mEntries">null</field>
  580.       <field name="mOffset">0</field>
  581.       <field name="mStartDate">null</field>
  582.       <field name="mEndDate">null</field>
  583.       <field name="mRange">0</field>
  584.       <field name="mStartHour">0</field>
  585.       <field name="mEndHour">24</field>
  586.       <field name="mForce24Hours">false</field>
  587.       <field name="mZoomFactor">100</field>
  588.  
  589.       <property name="zoomFactor">
  590.         <getter><![CDATA[
  591.           return this.mZoomFactor;
  592.         ]]></getter>
  593.         <setter><![CDATA[
  594.           this.mZoomFactor = val;
  595.  
  596.           var hours =
  597.               document.getAnonymousElementByAttribute(
  598.                   this, "anonid", "hours");
  599.           while (hours.childNodes.length > 1) {
  600.               hours.removeChild(hours.lastChild);
  601.           }
  602.  
  603.           this.onLoad();
  604.  
  605.           return val;
  606.         ]]></setter>
  607.       </property>
  608.  
  609.       <property name="force24Hours">
  610.         <getter><![CDATA[
  611.           return this.mForce24Hours;
  612.         ]]></getter>
  613.         <setter><![CDATA[
  614.           this.mForce24Hours = val;
  615.           this.initTimeRange();
  616.  
  617.           var hours =
  618.               document.getAnonymousElementByAttribute(
  619.                   this, "anonid", "hours");
  620.           while (hours.childNodes.length > 1) {
  621.               hours.removeChild(hours.lastChild);
  622.           }
  623.  
  624.           this.onLoad();
  625.  
  626.           return val;
  627.         ]]></setter>
  628.       </property>
  629.  
  630.       <property name="startDate">
  631.         <setter><![CDATA[
  632.           this.mStartDate = val.clone();
  633.           this.mStartDate.isDate = false;
  634.           this.mStartDate.makeImmutable();
  635.           return val;
  636.         ]]></setter>
  637.       </property>
  638.  
  639.       <property name="endDate">
  640.         <setter><![CDATA[
  641.           this.mEndDate = val.clone();
  642.           this.mEndDate.isDate = false;
  643.           this.mEndDate.makeImmutable();
  644.           return val;
  645.         ]]></setter>
  646.       </property>
  647.  
  648.       <property name="numHours">
  649.         <getter><![CDATA[
  650.           var numHours = this.mEndHour - this.mStartHour;
  651.           return Math.floor(numHours * 100 / this.mZoomFactor);
  652.         ]]></getter>
  653.       </property>
  654.  
  655.       <property name="contentWidth">
  656.         <getter><![CDATA[
  657.           var hours =
  658.               document.getAnonymousElementByAttribute(
  659.                   this, "anonid", "hours");
  660.           return (hours.childNodes[1].boxObject.x -
  661.                   hours.childNodes[0].boxObject.x) *
  662.                   this.numHours;
  663.         ]]></getter>
  664.       </property>
  665.  
  666.       <property name="containerWidth">
  667.         <getter><![CDATA[
  668.           // Step up the hierarchy until we reach the listbox
  669.           return this.parentNode.
  670.                       parentNode.
  671.                       parentNode.
  672.                       parentNode.boxObject.width;
  673.         ]]></getter>
  674.       </property>
  675.  
  676.       <property name="dayOffset">
  677.         <setter><![CDATA[
  678.           this.mOffset = val * this.numHours;
  679.           this.showState();
  680.           return val;
  681.         ]]></setter>
  682.       </property>
  683.  
  684.       <property name="documentSize">
  685.         <getter><![CDATA[
  686.           return  this.contentWidth * this.mRange;
  687.         ]]></getter>
  688.       </property>
  689.  
  690.       <property name="scroll">
  691.         <setter><![CDATA[
  692.           // How much pixels spans a single day
  693.           var oneday = this.contentWidth;
  694.           if (oneday <= 0) {
  695.               return val;
  696.           }
  697.  
  698.           // The difference in pixels between the content and the container.
  699.           var shift = (oneday * this.mRange) - (this.containerWidth);
  700.  
  701.           // Now calculate the (positive) offset in pixels which the content
  702.           // needs to be shifted. This is a simple scaling in one dimension.
  703.           var offset = Math.floor(val * shift);
  704.  
  705.           // Now find out how much days this offset effectively skips.
  706.           // this is a simple division which always yields a positive integer value.
  707.           this.dayOffset = (offset - (offset % oneday)) / oneday;
  708.  
  709.           // Set the pixel offset for the content which will always need
  710.           // to be in the range [0 <= offset <= oneday].
  711.           offset %= oneday;
  712.  
  713.           // Set the offset at the content node.
  714.           var container =
  715.               document.getAnonymousElementByAttribute(
  716.                   this, "anonid", "container");
  717.           container.x = offset;
  718.           return val;
  719.         ]]></setter>
  720.       </property>
  721.  
  722.       <constructor><![CDATA[
  723.         this.initTimeRange();
  724.         this.mRange = Number(this.getAttribute("range"));
  725.         this.onLoad();
  726.       ]]></constructor>
  727.  
  728.       <method name="onLoad">
  729.         <body><![CDATA[
  730.           var numHours = this.mEndHour - this.mStartHour;
  731.           this.mState = new Array(this.mRange * numHours);
  732.           for (var i = 0; i < this.mState.length; i++) {
  733.               this.mState[i] = 0;
  734.           }
  735.  
  736.           var step_in_minutes = Math.floor(60 * this.mZoomFactor / 100);
  737.           var formatter = Components.classes[
  738.               "@mozilla.org/calendar/datetime-formatter;1"]
  739.                   .getService(
  740.                       Components.interfaces.calIDateTimeFormatter);
  741.           var date = jsDateToDateTime(new Date());
  742.           date.hour = this.mStartHour;
  743.           date.minute = 0;
  744.           var height = this.parentNode.parentNode.boxObject.height - 1;
  745.           var hours =
  746.               document.getAnonymousElementByAttribute(
  747.                   this, "anonid", "hours");
  748.           if (hours.childNodes.length <= 1) {
  749.               var template = hours.childNodes[0];
  750.               var time = formatter.formatTime(date);
  751.               template.setAttribute("value", time);
  752.               date.minute += step_in_minutes;
  753.               // TODO: hardcoded value
  754.               var num_days = Math.max(2, 4 * this.mZoomFactor / 100);
  755.               var count = Math.ceil(
  756.                   (this.mEndHour - this.mStartHour) * 60 / step_in_minutes);
  757.               var remain = count - 1;
  758.               for (var day = 1; day <= num_days; day++) {
  759.                   while (remain--) {
  760.                       var newNode = template.cloneNode(false);
  761.                       var value = formatter.formatTime(date);
  762.                       newNode.setAttribute("value", value);
  763.                       hours.appendChild(newNode);
  764.                       date.minute += step_in_minutes;
  765.                   }
  766.                   date.hour = this.mStartHour;
  767.                   date.day++;
  768.                   remain = count;
  769.               }
  770.           }
  771.         ]]></body>
  772.       </method>
  773.  
  774.       <method name="onFreeBusy">
  775.         <parameter name="aEntries"/>
  776.         <body><![CDATA[
  777.           // The argument denotes the requested freebusy intervals.
  778.           // We need to set our state array according to this
  779.           // result. After the state has been updated we call showState()
  780.           // which will map the entries to attributes on the xul elements.
  781.           if (aEntries) {
  782.               // Remember the free/busy array which is used to find a
  783.               // new time for an event. We store this array only if
  784.               // the provider returned a valid array. In any other case
  785.               // (temporarily clean the display) we keep the last know result.
  786.               this.mEntries = aEntries;
  787.  
  788.               var kDefaultTimezone = calendarDefaultTimezone();
  789.  
  790.               var start = this.mStartDate.clone();
  791.               start.hour = 0;
  792.               start.minute = 0;
  793.               start.second = 0;
  794.               start.timezone = kDefaultTimezone;
  795.               var end = start.clone();
  796.               end.day += this.mRange;
  797.               end.timezone = kDefaultTimezone;
  798.  
  799.               // First of all set all state slots to 'free'
  800.               for (var i = 0; i < this.mState.length; i++) {
  801.                   this.mState[i] = 1;
  802.               }
  803.  
  804.               var numHours = this.numHours;
  805.  
  806.               // Iterate all incoming freebusy entries
  807.               for each (var entry in aEntries) {
  808.                   var rangeStart = entry.interval.start.getInTimezone(kDefaultTimezone);
  809.                   var rangeEnd = entry.interval.end.getInTimezone(kDefaultTimezone);
  810.  
  811.                   if (rangeStart.compare(start) >= 0 &&
  812.                       rangeEnd.compare(end) < 0) {
  813.                       var rangeDuration = rangeEnd.subtractDate(rangeStart);
  814.                       var rangeStartHour = rangeStart.hour;
  815.                       var rangeEndHour = rangeStartHour + (rangeDuration.inSeconds / 3600);
  816.  
  817.                       if ((rangeStartHour < this.mEndHour) &&
  818.                           (rangeEndHour >= this.mStartHour)) {
  819.                           var dayingrid = start.clone();
  820.                           dayingrid.year = rangeStart.year;
  821.                           dayingrid.month = rangeStart.month;
  822.                           dayingrid.day = rangeStart.day;
  823.                           dayingrid.getInTimezone(kDefaultTimezone);
  824.  
  825.                           // Ok, this is an entry we're interested in. Find out
  826.                           // which hours are actually occupied.
  827.                           var offset = rangeStart.subtractDate(dayingrid);
  828.  
  829.                           // Calculate how many days we're offset from the
  830.                           // start of the grid. Eliminate hours in case
  831.                           // we encounter the daylight-saving hop.
  832.                           var dayoffset = dayingrid.subtractDate(start);
  833.                           dayoffset.hours = 0;
  834.  
  835.                           // Add both offsets to find the total offset.
  836.                           // dayoffset -> offset in days from start of grid
  837.                           // offset -> offset in hours from start of current day
  838.                           offset.addDuration(dayoffset);
  839.  
  840.                           var duration = rangeEnd.subtractDate(rangeStart);
  841.                           var start_in_minutes = Math.floor(offset.inSeconds / 60);
  842.                           var end_in_minutes = Math.ceil((duration.inSeconds / 60) +
  843.                                                          (offset.inSeconds / 60));
  844.  
  845.                           function minute2offset(value,
  846.                                                  fNumHours,
  847.                                                  numHours,
  848.                                                  start_hour,
  849.                                                  zoomfactor) {
  850.                               // 'value' is some integer in the interval [0, range * 24 * 60].
  851.                               // we need to map this offset into our array which
  852.                               // holds elements for 'range' days with [start, end] hours each.
  853.                               var minutes_per_day = 24 * 60;
  854.                               var day = (value - (value % minutes_per_day)) /
  855.                                         minutes_per_day;
  856.                               var minute = Math.floor(
  857.                                   value % minutes_per_day) -
  858.                                   (start_hour * 60);
  859.  
  860.                               minute = Math.max(0, minute);
  861.  
  862.                               if (minute >= (numHours * 60)) {
  863.                                   minute = (numHours * 60) - 1;
  864.                               }
  865.                               // How to get from minutes to offset?
  866.                               // 60 = 100%, 30 = 50%, 15 = 25%, etc.
  867.                               var minutes_per_block = 60 * zoomfactor / 100;
  868.  
  869.                               var block = Math.floor(minute / minutes_per_block);
  870.  
  871.                               return Math.ceil(fNumHours) * day + block;
  872.                           }
  873.  
  874.                           // Number of hours (fractional representation)
  875.                           var numHours = this.mEndHour - this.mStartHour;
  876.                           var fNumHours = numHours * 100 / this.mZoomFactor;
  877.  
  878.                           var start_offset =
  879.                               minute2offset(start_in_minutes,
  880.                                             fNumHours,
  881.                                             numHours,
  882.                                             this.mStartHour,
  883.                                             this.mZoomFactor);
  884.                           var end_offset =
  885.                               minute2offset(end_in_minutes - 1,
  886.                                             fNumHours,
  887.                                             numHours,
  888.                                             this.mStartHour,
  889.                                             this.mZoomFactor);
  890.  
  891.                           // Set all affected state slots to 'busy'
  892.                           for (var i = start_offset; i <= end_offset; i++) {
  893.                               this.mState[i] = 2;
  894.                           }
  895.                       }
  896.                   }
  897.               }
  898.           } else {
  899.               // First of all set all state slots to 'unknown'
  900.               for (var i = 0; i < this.mState.length; i++) {
  901.                   this.mState[i] = 0;
  902.               }
  903.           }
  904.  
  905.           this.showState();
  906.         ]]></body>
  907.       </method>
  908.  
  909.       <method name="showState">
  910.         <body><![CDATA[
  911.           var hours =
  912.               document.getAnonymousElementByAttribute(
  913.                   this, "anonid", "hours");
  914.           for (var i = 0; i < hours.childNodes.length; i++) {
  915.               var hour = hours.childNodes[i];
  916.               var state = this.mState[i + this.mOffset];
  917.               if (state == 0) {
  918.                   hour.removeAttribute("state");
  919.               } else if (state == 1) {
  920.                   hour.setAttribute("state", "free");
  921.               } else if (state == 2) {
  922.                   hour.setAttribute("state", "busy");
  923.               }
  924.           }
  925.         ]]></body>
  926.       </method>
  927.  
  928.       <method name="nextSlot">
  929.         <parameter name="aStartTime"/>
  930.         <parameter name="aEndTime"/>
  931.         <parameter name="allDay"/>
  932.         <body><![CDATA[
  933.           var newTime = aStartTime.clone();
  934.           var duration = aEndTime.subtractDate(aStartTime);
  935.           var newEndTime = newTime.clone();
  936.           newEndTime.addDuration(duration);
  937.  
  938.           var kDefaultTimezone = calendarDefaultTimezone();
  939.  
  940.           if (this.mEntries) {
  941.               for each (var entry in this.mEntries) {
  942.                 var rangeStart =
  943.                     entry.interval.start.getInTimezone(kDefaultTimezone);
  944.                 var rangeEnd =
  945.                     entry.interval.end.getInTimezone(kDefaultTimezone);
  946.  
  947.                 var isZeroLength = !newTime.compare(newEndTime);
  948.                 if ((isZeroLength &&
  949.                      newTime.compare(rangeStart) >= 0 &&
  950.                      newTime.compare(rangeEnd) < 0) ||
  951.                     (!isZeroLength &&
  952.                      newTime.compare(rangeEnd) < 0 &&
  953.                      newEndTime.compare(rangeStart) > 0)) {
  954.                     // Current range of event conflicts with another event.
  955.                     // we need to find a new time for this event. A trivial approach
  956.                     // is to set the new start-time of the event equal to the end-time
  957.                     // of the conflict-range. All-day events need to be considered
  958.                     // separately, in which case we skip to the next day.
  959.                     newTime = rangeEnd.clone();
  960.                     if (allDay) {
  961.                         if (!((newTime.hour == 0) &&
  962.                              (newTime.minute == 0) &&
  963.                              (newTime.second == 0))) {
  964.                             newTime.day++;
  965.                             newTime.hour = 0;
  966.                             newTime.minute = 0;
  967.                             newTime.second = 0;
  968.                         }
  969.                     }
  970.                     newEndTime = newTime.clone();
  971.                     newEndTime.addDuration(duration);
  972.                 }
  973.               }
  974.           }
  975.  
  976.           return newTime;
  977.         ]]></body>
  978.       </method>
  979.  
  980.       <method name="initTimeRange">
  981.         <body><![CDATA[
  982.           if (this.force24Hours) {
  983.               this.mStartHour = 0;
  984.               this.mEndHour = 24;
  985.           } else {
  986.               this.mStartHour = getPrefSafe("calendar.view.daystarthour", 8);
  987.               this.mEndHour = getPrefSafe("calendar.view.dayendhour", 19);
  988.           }
  989.         ]]></body>
  990.       </method>
  991.     </implementation>
  992.   </binding>
  993.  
  994.   <!-- ############################################################################# -->
  995.   <!-- 'freebusy-grid'-binding                                                       -->
  996.   <!-- ############################################################################# -->
  997.  
  998.   <!-- id's are evil, use anonid -->
  999.   <binding id="freebusy-grid">
  1000.     <content>
  1001.       <xul:listbox anonid="listbox"
  1002.                    seltype="multiple"
  1003.                    rows="-1"
  1004.                    disabled="true"
  1005.                    flex="1"
  1006.                    style="min-width: 50em; min-height: 30em">
  1007.         <xul:listcols>
  1008.           <xul:listcol id="grid-column" flex="1"/>
  1009.         </xul:listcols>
  1010.         <xul:listitem anonid="item"
  1011.                       class="addressingWidgetItem"
  1012.                       allowevents="true">
  1013.           <xul:listcell class="addressingWidgetCell">
  1014.             <xul:freebusy-row id="attendeeCol4#1"
  1015.                               anonid="grid"
  1016.                               dirty="true"
  1017.                               xbl:inherits="range"/>
  1018.           </xul:listcell>
  1019.         </xul:listitem>
  1020.       </xul:listbox>
  1021.     </content>
  1022.  
  1023.     <implementation>
  1024.       <field name="mContentHeight">0</field>
  1025.       <field name="mRowHeight">0</field>
  1026.       <field name="mNumColumns">0</field>
  1027.       <field name="mMaxFreeBusy">0</field>
  1028.       <field name="mPendingRequests">null</field>
  1029.       <field name="mStartDate">null</field>
  1030.       <field name="mEndDate">null</field>
  1031.       <field name="mScrollOffset">0</field>
  1032.       <field name="mRange">0</field>
  1033.       <field name="mStartHour">0</field>
  1034.       <field name="mEndHour">24</field>
  1035.       <field name="mForce24Hours">false</field>
  1036.       <field name="mZoomFactor">100</field>
  1037.  
  1038.       <property name="zoomFactor">
  1039.         <getter><![CDATA[
  1040.           return this.mZoomFactor;
  1041.         ]]></getter>
  1042.         <setter><![CDATA[
  1043.           this.mZoomFactor = val;
  1044.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1045.               var freebusy = this.getFreeBusyElement(i);
  1046.               freebusy.zoomFactor = this.mZoomFactor;
  1047.           }
  1048.           this.forceRefresh();
  1049.           return val;
  1050.         ]]></setter>
  1051.       </property>
  1052.  
  1053.       <property name="force24Hours">
  1054.         <getter><![CDATA[
  1055.           return this.mForce24Hours;
  1056.         ]]></getter>
  1057.         <setter><![CDATA[
  1058.           this.mForce24Hours = val;
  1059.           this.initTimeRange();
  1060.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1061.               var freebusy = this.getFreeBusyElement(i);
  1062.               freebusy.force24Hours = this.mForce24Hours;
  1063.           }
  1064.           return val;
  1065.         ]]></setter>
  1066.       </property>
  1067.  
  1068.       <property name="firstVisibleRow">
  1069.         <getter><![CDATA[
  1070.           var listbox =
  1071.               document.getAnonymousElementByAttribute(
  1072.                   this, "anonid", "listbox");
  1073.           return listbox.getIndexOfFirstVisibleRow();
  1074.         ]]></getter>
  1075.         <setter><![CDATA[
  1076.           var listbox =
  1077.               document.getAnonymousElementByAttribute(
  1078.                   this, "anonid", "listbox");
  1079.           listbox.scrollToIndex(val);
  1080.           return val;
  1081.         ]]></setter>
  1082.       </property>
  1083.  
  1084.       <property name="ratio">
  1085.         <setter><![CDATA[
  1086.           var listbox =
  1087.               document.getAnonymousElementByAttribute(
  1088.                   this, "anonid", "listbox");
  1089.           var rowcount = listbox.getRowCount();
  1090.           listbox.scrollToIndex(Math.floor(rowcount * val));
  1091.           return val;
  1092.         ]]></setter>
  1093.       </property>
  1094.  
  1095.       <constructor><![CDATA[
  1096.         var args = window.arguments[0];
  1097.  
  1098.         this.initTimeRange();
  1099.  
  1100.         this.mRange = Number(this.getAttribute("range"));
  1101.  
  1102.         this.mMaxFreeBusy = 0;
  1103.         this.mPendingRequests = [];
  1104.  
  1105.         var self = this;
  1106.         var load = function freebusy_grid_loadHandler() {
  1107.             self.onLoad();
  1108.         };
  1109.         window.addEventListener("load", load, true);
  1110.         var unload = function freebusy_grid_unloadHandler() {
  1111.             self.onUnload();
  1112.         };
  1113.         window.addEventListener("unload", unload, true);
  1114.       ]]></constructor>
  1115.  
  1116.       <property name="startDate">
  1117.         <getter><![CDATA[
  1118.           return this.mStartDate;
  1119.         ]]></getter>
  1120.         <setter><![CDATA[
  1121.           this.mStartDate = val.clone();
  1122.           this.mStartDate.makeImmutable();
  1123.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1124.               this.getFreeBusyElement(i).startDate = val;
  1125.           }
  1126.           return val;
  1127.         ]]></setter>
  1128.       </property>
  1129.  
  1130.       <property name="endDate">
  1131.         <getter><![CDATA[
  1132.           return this.mEndDate;
  1133.         ]]></getter>
  1134.         <setter><![CDATA[
  1135.           this.mEndDate = val.clone();
  1136.           this.mEndDate.makeImmutable();
  1137.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1138.               this.getFreeBusyElement(i).endDate = val;
  1139.           }
  1140.           return val;
  1141.         ]]></setter>
  1142.       </property>
  1143.  
  1144.       <property name="documentSize">
  1145.         <getter><![CDATA[
  1146.           return this.getFreeBusyElement(1).documentSize;
  1147.         ]]></getter>
  1148.       </property>
  1149.  
  1150.       <method name="onLoad">
  1151.         <body><![CDATA[
  1152.           this.onInitialize();
  1153.         ]]></body>
  1154.       </method>
  1155.  
  1156.       <method name="onUnload">
  1157.         <body><![CDATA[
  1158.           // Cancel pending free/busy requests
  1159.           for each (var request in this.mPendingRequests) {
  1160.               request.cancel(null);
  1161.           }
  1162.  
  1163.           this.mPendingRequests = [];
  1164.         ]]></body>
  1165.       </method>
  1166.  
  1167.       <method name="onInitialize">
  1168.         <body><![CDATA[
  1169.           var args = window.arguments[0];
  1170.           var startTime = args.startTime;
  1171.           var endTime = args.endTime;
  1172.           var calendar = args.calendar;
  1173.  
  1174.           var kDefaultTimezone = calendarDefaultTimezone();
  1175.           this.startDate = startTime.getInTimezone(kDefaultTimezone);
  1176.           this.endDate = endTime.getInTimezone(kDefaultTimezone);
  1177.  
  1178.           var listbox =
  1179.               document.getAnonymousElementByAttribute(
  1180.                   this, "anonid", "listbox");
  1181.           var template =
  1182.               document.getAnonymousElementByAttribute(
  1183.                   this, "anonid", "item");
  1184.           this.appendNewRow(listbox, template, null);
  1185.           listbox.removeChild(template);
  1186.  
  1187.           this.updateFreeBusy();
  1188.         ]]></body>
  1189.       </method>
  1190.  
  1191.       <method name="onChangeCalendar">
  1192.         <parameter name="calendar"/>
  1193.         <body><![CDATA[
  1194.         ]]></body>
  1195.       </method>
  1196.  
  1197.       <!-- appends a new empty row -->
  1198.       <method name="appendNewRow">
  1199.         <parameter name="aParentNode"/>
  1200.         <parameter name="aTemplateNode"/>
  1201.         <parameter name="aReplaceNode"/>
  1202.         <body><![CDATA[
  1203.           this.mMaxFreeBusy++;
  1204.           var newNode = aTemplateNode.cloneNode(true);
  1205.           if (aReplaceNode) {
  1206.               aParentNode.replaceChild(newNode, aReplaceNode);
  1207.           } else {
  1208.               aParentNode.appendChild(newNode);
  1209.           }
  1210.  
  1211.           var grid =
  1212.               document.getAnonymousElementByAttribute(
  1213.                   newNode, "anonid", "grid");
  1214.           var rowNumber = this.mMaxFreeBusy;
  1215.           if (rowNumber >= 0) {
  1216.               grid.setAttribute("id", "attendeeCol4#" + rowNumber);
  1217.           }
  1218.  
  1219.           // Propagate start/enddate to the new row.
  1220.           grid.startDate = this.mStartDate;
  1221.           grid.endDate = this.mEndDate;
  1222.  
  1223.           grid.force24Hours = this.mForce24Hours;
  1224.           grid.zoomFactor = this.mZoomFactor;
  1225.  
  1226.           // We always clone the first row.  The problem is that the first row
  1227.           // could be focused.  When we clone that row, we end up with a cloned
  1228.           // XUL textbox that has a focused attribute set.  Therefore we think
  1229.           // we're focused and don't properly refocus.  The best solution to this
  1230.           // would be to clone a template row that didn't really have any presentation,
  1231.           // rather than using the real visible first row of the listbox.
  1232.           // For now we'll just put in a hack that ensures the focused attribute
  1233.           // is never copied when the node is cloned.
  1234.           if (grid.getAttribute('focused') != '') {
  1235.               grid.removeAttribute('focused');
  1236.           }
  1237.         ]]></body>
  1238.       </method>
  1239.  
  1240.       <property name="scroll">
  1241.         <setter><![CDATA[
  1242.           this.mScrollOffset = val;
  1243.           for (i = 1; i <= this.mMaxFreeBusy; i++) {
  1244.               this.getFreeBusyElement(i).scroll = val;
  1245.           }
  1246.           return val;
  1247.         ]]></setter>
  1248.       </property>
  1249.  
  1250.       <method name="onModify">
  1251.         <parameter name="event"/>
  1252.         <body><![CDATA[
  1253.           // Add or remove rows depending on the number of items
  1254.           // contained in the list passed as argument.
  1255.           var list = event.details;
  1256.           if (this.mMaxFreeBusy != list.length) {
  1257.               var listbox =
  1258.                   document.getAnonymousElementByAttribute(
  1259.                       this, "anonid", "listbox");
  1260.               var template =
  1261.                   document.getAnonymousElementByAttribute(
  1262.                       this, "anonid", "item");
  1263.               while (this.mMaxFreeBusy < list.length) {
  1264.                   var nextDummy = this.getNextDummyRow();
  1265.                   this.appendNewRow(listbox, template, nextDummy);
  1266.                   template =
  1267.                       document.getAnonymousElementByAttribute(
  1268.                           this, "anonid", "item");
  1269.               }
  1270.               while (this.mMaxFreeBusy > list.length) {
  1271.                   this.deleteRow(this.mMaxFreeBusy);
  1272.               }
  1273.           }
  1274.  
  1275.           // Store the attributes in our grid rows.
  1276.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1277.               var freebusy = this.getFreeBusyElement(i);
  1278.               freebusy.setAttribute("calid", list[i - 1].calid);
  1279.               freebusy.removeAttribute("dirty");
  1280.               if (list[i - 1].dirty) {
  1281.                   freebusy.setAttribute("dirty", "true");
  1282.               }
  1283.           }
  1284.  
  1285.           // Align all rows
  1286.           this.scroll = this.mScrollOffset;
  1287.  
  1288.           this.updateFreeBusy();
  1289.         ]]></body>
  1290.       </method>
  1291.  
  1292.       <!-- updateFreeBusy(), implementation of the core functionality of this binding -->
  1293.       <method name="updateFreeBusy">
  1294.         <body><![CDATA[
  1295.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1296.               // Retrieve the string from the appropriate row
  1297.               var freebusy = this.getFreeBusyElement(i);
  1298.               if (freebusy.hasAttribute("dirty")) {
  1299.                   freebusy.removeAttribute("dirty");
  1300.                   var calid = freebusy.getAttribute("calid");
  1301.                   if (calid && calid.length > 0) {
  1302.                       // Define the datetime range we would like to ask for.
  1303.                       var start = this.mStartDate.clone();
  1304.                       start.hour = 0;
  1305.                       start.minute = 0;
  1306.                       start.second = 0;
  1307.                       var end = start.clone();
  1308.                       end.day += this.mRange;
  1309.                       // Update with 'no data available' until response will be received
  1310.                       freebusy.onFreeBusy(null);
  1311.                       try {
  1312.                           var fbService = Components.classes["@mozilla.org/calendar/freebusy-service;1"]
  1313.                                                     .getService(Components.interfaces.calIFreeBusyService);
  1314.                           var fbListener = {
  1315.                               row: i,
  1316.                               binding: this,
  1317.                               // calIGenericOperationListener:
  1318.                               onResult: function mFBL_onResult(request, entries) {
  1319.                                   if (!request || !request.isPending) {
  1320.                                       // Find request in list of pending requests and remove from queue:
  1321.                                       function neq(op) {
  1322.                                           return (request.id != op.id);
  1323.                                       }
  1324.                                       this.binding.mPendingRequests = this.binding.mPendingRequests.filter(neq);
  1325.                                   }
  1326.                                   if (entries) {
  1327.                                       var freebusy = this.binding.getFreeBusyElement(this.row);
  1328.                                       freebusy.onFreeBusy(entries);
  1329.                                   }
  1330.                               }
  1331.                           };
  1332.                           var request = fbService.getFreeBusyIntervals(
  1333.                               calid, start, end, Components.interfaces.calIFreeBusyInterval.BUSY_ALL,
  1334.                               fbListener);
  1335.                           if (request.isPending) {
  1336.                               this.mPendingRequests.push(request);
  1337.                           }
  1338.                       }
  1339.                       catch (ex) {
  1340.                           Components.utils.reportError(ex);
  1341.                       }
  1342.                   }
  1343.               }
  1344.           }
  1345.         ]]></body>
  1346.       </method>
  1347.  
  1348.       <method name="nextSlot">
  1349.         <body><![CDATA[
  1350.           var startTime = this.mStartDate.clone();
  1351.           var endTime = this.mEndDate.clone();
  1352.  
  1353.           startTime.isDate = false;
  1354.           endTime.isDate = false;
  1355.  
  1356.           var allDay = this.mStartDate.isDate;
  1357.           var step_in_minutes = Math.floor(60 * this.zoomFactor / 100);
  1358.           if (allDay) {
  1359.               step_in_minutes = 60 * 24;
  1360.               endTime.day++;
  1361.           }
  1362.  
  1363.           var duration = endTime.subtractDate(startTime);
  1364.  
  1365.           startTime.minute += step_in_minutes;
  1366.  
  1367.           if (startTime.hour < this.mStartHour) {
  1368.               startTime.hour = this.mStartHour;
  1369.               startTime.minute = 0;
  1370.           }
  1371.  
  1372.           endTime = startTime.clone();
  1373.           endTime.addDuration(duration);
  1374.           if (endTime.hour > this.mEndHour) {
  1375.               startTime.day++;
  1376.               startTime.hour = this.mStartHour;
  1377.               startTime.minute = 0;
  1378.               endTime = startTime.clone();
  1379.               endTime.addDuration(duration);
  1380.               if (endTime.hour > this.mEndHour) {
  1381.                   return this.mStartDate.clone();
  1382.               }
  1383.           }
  1384.  
  1385.           // Now iterate all freebusy-rows and ask each one
  1386.           // if it wants to modify the suggested time slot.
  1387.           // we keep iterating the rows until all of them
  1388.           // are happy with it.
  1389.           var recheck;
  1390.           do {
  1391.               recheck = false;
  1392.  
  1393.               for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1394.                   var row = this.getFreeBusyElement(i);
  1395.                   var newTime = row.nextSlot(startTime, endTime, allDay);
  1396.                   if (newTime) {
  1397.                     if (newTime.compare(startTime) != 0) {
  1398.                         startTime = newTime;
  1399.  
  1400.                         if (startTime.hour < this.mStartHour) {
  1401.                             startTime.hour = this.mStartHour;
  1402.                             startTime.minute = 0;
  1403.                         }
  1404.  
  1405.                         endTime = startTime.clone();
  1406.                         endTime.addDuration(duration);
  1407.  
  1408.                         if (endTime.hour > this.mEndHour) {
  1409.                             startTime.day++;
  1410.                             startTime.hour = this.mStartHour;
  1411.                             startTime.minute = 0;
  1412.                             endTime = startTime.clone();
  1413.                             endTime.addDuration(duration);
  1414.                         }
  1415.  
  1416.                         recheck = true;
  1417.                     }
  1418.                 } else {
  1419.                     // A new slot could not be found
  1420.                     // and the given time was also invalid.
  1421.                     return this.mStartDate.clone();
  1422.                 }
  1423.               }
  1424.           } while (recheck);
  1425.  
  1426.           // Return the unmodifed startdate of the item
  1427.           // in case no possible match was found.
  1428.           if (startTime.compare(this.mStartDate) == 0) {
  1429.               return this.mStartDate.clone();
  1430.           }
  1431.  
  1432.           // Special case for allday events - if the original
  1433.           // datetime was indeed a date we need to carry this
  1434.           // state over to the calculated datetime.
  1435.           if (this.mStartDate.isDate) {
  1436.               startTime.isDate = true;
  1437.           }
  1438.  
  1439.           // In case the new starttime happens to be scheduled
  1440.           // on a different day, we also need to update the
  1441.           // complete freebusy informations and appropriate
  1442.           // underlying arrays holding the informaion.
  1443.           if (this.mStartDate.day != startTime.day) {
  1444.               for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1445.                   var row = this.getFreeBusyElement(i);
  1446.                   row.setAttribute("dirty", "true");
  1447.               }
  1448.               this.updateFreeBusy();
  1449.           }
  1450.  
  1451.           // Return the new starttime of the item.
  1452.           return startTime;
  1453.         ]]></body>
  1454.       </method>
  1455.  
  1456.       <method name="forceRefresh">
  1457.         <body><![CDATA[
  1458.           for (var i = 1; i <= this.mMaxFreeBusy; i++) {
  1459.               var row = this.getFreeBusyElement(i);
  1460.               row.setAttribute("dirty", "true");
  1461.           }
  1462.           this.updateFreeBusy();
  1463.         ]]></body>
  1464.       </method>
  1465.  
  1466.       <!-- This method returns the <xul:listitem> at row numer 'aRow' -->
  1467.       <method name="getListItem">
  1468.         <parameter name="aRow"/>
  1469.         <body><![CDATA[
  1470.           var listbox =
  1471.               document.getAnonymousElementByAttribute(
  1472.                   this, "anonid", "listbox");
  1473.           if (listbox && aRow > 0) {
  1474.               var listitems = listbox.getElementsByTagNameNS('*', 'listitem');
  1475.               if (listitems && listitems.length >= aRow) {
  1476.                   return listitems[aRow - 1];
  1477.               }
  1478.           }
  1479.           return 0;
  1480.         ]]></body>
  1481.       </method>
  1482.  
  1483.       <method name="getFreeBusyElement">
  1484.         <parameter name="aRow"/>
  1485.         <body><![CDATA[
  1486.           return document.getElementById("attendeeCol4#" + aRow);
  1487.         ]]></body>
  1488.       </method>
  1489.  
  1490.       <method name="deleteRow">
  1491.         <parameter name="aRow"/>
  1492.         <body><![CDATA[
  1493.           // Reset id's in order to not break the sequence
  1494.           var max = this.mMaxFreeBusy;
  1495.           this.removeRow(aRow);
  1496.           var numberOfCols = this.numColumns;
  1497.           for (var row = aRow + 1; row <= max; row++) {
  1498.               for (var col = 1; col <= numberOfCols; col++) {
  1499.                   var colID = "attendeeCol" + col + "#" + row;
  1500.                   var elem = document.getElementById(colID);
  1501.                   if (elem) {
  1502.                       elem.setAttribute(
  1503.                           "id",
  1504.                           "attendeeCol" + (col) + "#" + (row - 1));
  1505.                   }
  1506.               }
  1507.           }
  1508.         ]]></body>
  1509.       </method>
  1510.  
  1511.       <method name="removeRow">
  1512.         <parameter name="aRow"/>
  1513.         <body><![CDATA[
  1514.           var listbox =
  1515.               document.getAnonymousElementByAttribute(
  1516.                   this, "anonid", "listbox");
  1517.           var nodeToRemove = this.getListItem(aRow)
  1518.           nodeToRemove.parentNode.removeChild(nodeToRemove);
  1519.           this.fitDummyRows();
  1520.           this.mMaxFreeBusy--;
  1521.         ]]></body>
  1522.       </method>
  1523.  
  1524.       <!-- gets the next row from the top down -->
  1525.       <method name="getNextDummyRow">
  1526.         <body><![CDATA[
  1527.           var listbox =
  1528.               document.getAnonymousElementByAttribute(
  1529.                   this, "anonid", "listbox");
  1530.           var kids = listbox.childNodes;
  1531.           for (var i = 0; i < kids.length; ++i) {
  1532.               if (kids[i].hasAttribute("_isDummyRow")) {
  1533.                   return kids[i];
  1534.               }
  1535.           }
  1536.           return null;
  1537.         ]]></body>
  1538.       </method>
  1539.  
  1540.       <method name="fitDummyRows">
  1541.         <body><![CDATA[
  1542.           var self = this;
  1543.           var func = function func() {
  1544.               self.calcContentHeight();
  1545.               self.createOrRemoveDummyRows();
  1546.           }
  1547.           setTimeout(func, 0);
  1548.         ]]></body>
  1549.       </method>
  1550.  
  1551.       <method name="calcContentHeight">
  1552.         <body><![CDATA[
  1553.           var listbox =
  1554.               document.getAnonymousElementByAttribute(
  1555.                   this, "anonid", "listbox");
  1556.           var items = listbox.getElementsByTagNameNS('*', 'listitem');
  1557.           this.mContentHeight = 0;
  1558.           if (items.length > 0) {
  1559.               var i = 0;
  1560.               do {
  1561.                   this.mRowHeight = items[i].boxObject.height;
  1562.                   ++i;
  1563.               } while (i < items.length && !this.mRowHeight);
  1564.               this.mContentHeight = this.mRowHeight * items.length;
  1565.           }
  1566.         ]]></body>
  1567.       </method>
  1568.  
  1569.       <method name="createOrRemoveDummyRows">
  1570.         <body><![CDATA[
  1571.           var listbox =
  1572.               document.getAnonymousElementByAttribute(
  1573.                   this, "anonid", "listbox");
  1574.           var listboxHeight = listbox.boxObject.height;
  1575.  
  1576.           // Remove rows to remove scrollbar
  1577.           var kids = listbox.childNodes;
  1578.           for (var i = kids.length - 1; this.mContentHeight > listboxHeight && i >= 0; --i) {
  1579.               if (kids[i].hasAttribute("_isDummyRow")) {
  1580.                   this.mContentHeight -= this.mRowHeight;
  1581.                   listbox.removeChild(kids[i]);
  1582.               }
  1583.           }
  1584.  
  1585.           // Add rows to fill space
  1586.           if (this.mRowHeight) {
  1587.               while (this.mContentHeight + this.mRowHeight < listboxHeight) {
  1588.                   this.createDummyItem(listbox);
  1589.                   this.mContentHeight += this.mRowHeight;
  1590.               }
  1591.           }
  1592.         ]]></body>
  1593.       </method>
  1594.  
  1595.       <method name="createDummyCell">
  1596.         <parameter name="aParent"/>
  1597.         <body><![CDATA[
  1598.           var cell = document.createElement("listcell");
  1599.           cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
  1600.           if (aParent) {
  1601.               aParent.appendChild(cell);
  1602.           }
  1603.           return cell;
  1604.         ]]></body>
  1605.       </method>
  1606.  
  1607.       <method name="createDummyItem">
  1608.         <parameter name="aParent"/>
  1609.         <body><![CDATA[
  1610.           var titem = document.createElement("listitem");
  1611.           titem.setAttribute("_isDummyRow", "true");
  1612.           titem.setAttribute("class", "dummy-row");
  1613.           for (var i = this.numColumns; i > 0; i--) {
  1614.               this.createDummyCell(titem);
  1615.           }
  1616.           if (aParent) {
  1617.               aParent.appendChild(titem);
  1618.           }
  1619.           return titem;
  1620.         ]]></body>
  1621.       </method>
  1622.  
  1623.       <property name="numColumns">
  1624.         <getter><![CDATA[
  1625.           if (!this.mNumColumns) {
  1626.               var listbox =
  1627.                   document.getAnonymousElementByAttribute(
  1628.                       this, "anonid", "listbox");
  1629.               var listCols = listbox.getElementsByTagNameNS('*', 'listcol');
  1630.               this.mNumColumns = listCols.length || 1;
  1631.           }
  1632.           return this.mNumColumns;
  1633.         ]]></getter>
  1634.       </property>
  1635.  
  1636.       <method name="initTimeRange">
  1637.         <body><![CDATA[
  1638.           if (this.force24Hours) {
  1639.               this.mStartHour = 0;
  1640.               this.mEndHour = 24;
  1641.           } else {
  1642.               this.mStartHour = getPrefSafe("calendar.view.daystarthour", 8);
  1643.               this.mEndHour = getPrefSafe("calendar.view.dayendhour", 19);
  1644.           }
  1645.         ]]></body>
  1646.       </method>
  1647.     </implementation>
  1648.   </binding>
  1649. </bindings>
  1650.